/**
 * CCodeGenerator: CCodeGenerator.java
 * CIS 706  Advisor Dr. Robby 
 *  * Team Project
 *  Heqing Huang && Rui Zhuang
 */

package sjc.codegen.extended;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.MethodInvocation;

import sjc.annotation.NonNull;
import sjc.annotation.NonNullElements;
import sjc.annotation.ReadOnly;
import sjc.annotation.ReadOnlyElements;
import sjc.symboltable.SymbolTable;
import sjc.symboltable.extended.ExtendedSymbolTable;
import sjc.type.ArrayType;
import sjc.type.BooleanType;
import sjc.type.IntType;
import sjc.type.Type;
import sjc.type.VoidType;
import sjc.type.checker.TypeTable;
import sjc.type.checker.extended.ExtendedTypeTable;
import sjc.util.Pair;

import org.stringtemplate.v4.*;

/**
 * This class is used to translate an ExtendedStaticJava {@link CompilationUnit}
 * to {@link ExtendedClassByteCodes} that represent a Java 1.5 class files.
 * 
 * @author <a href="mailto:robby@cis.ksu.edu">Robby</a>
 */
public class CCodeGenerator {
	/**
	 * Declared as protected to disallow creation of this object outside from
	 * the methods of this class.
	 */
	protected CCodeGenerator() {
	}

	public static class Error extends RuntimeException {
		/**
		 * Holds the {@link ASTNode} that causes this error.
		 */
		public final @NonNull
		@ReadOnly
		ASTNode node;

		/**
		 * Constructs an error of bytecode generation.
		 * 
		 * @param node
		 *            The {@link ASTNode} that causes this error.
		 * @param msg
		 *            The message that indicates the cause of this error.
		 */
		public Error(@NonNull final ASTNode node, @NonNull final String msg) {
			super(msg);
			assert (msg != null) && (node != null);
			this.node = node;
		}
	}

	/**
	 * Generates a {@link ExtendedClassByteCodes} that represents the class
	 * files for the given ExtendedStaticJava {@link CompilationUnit} with the
	 * given {@link ExtendedSymbolTable} and {@link ExtendedTypeTable}.
	 * 
	 * @param cu
	 *            The StaticJava {@link CompilationUnit}.
	 * @param st
	 *            The {@link ExtendedSymbolTable} of the {@link CompilationUnit}
	 *            .
	 * @param tt
	 *            The {@link ExtendedTypeTable} of the {@link CompilationUnit}.
	 * @return The {@link ExtendedClassByteCodes} that represents the class
	 *         files for the given ExtendedStaticJava {@link CompilationUnit}
	 *         with the given {@link ExtendedSymbolTable} and
	 *         {@link ExtendedTypeTable}.
	 * @throws ByteCodeGenerator.Error
	 *             If the generator encounter unexpected error.
	 * @throws FileNotFoundException
	 * @throws FileNotFoundException
	 */
	public static @NonNull
	String generate(@NonNull final CompilationUnit cu,
			@NonNull final SymbolTable st, @NonNull final TypeTable tt)
			throws CCodeGenerator.Error, FileNotFoundException {
		assert (cu != null) && (st != null) && (tt != null);
		final Visitor v = new Visitor(st, tt);
		cu.accept(v);
		// the result string is written to a file
		final String result = v.ccodestring + "\r\n" + v.mainstring;
		v.dispose();
		return result;
	}

	/**
	 * The visitor for {@link ASTNode} to generate bytecodes.
	 * 
	 * @author <a href="mailto:robby@cis.ksu.edu">Robby</a>
	 */
	protected static class Visitor extends ASTVisitor {
		protected @NonNullElements
		@ReadOnlyElements
		Map<ASTNode, Object> symbolMap;

		protected @NonNullElements
		@ReadOnlyElements
		Map<ASTNode, Type> typeMap;

		protected @NonNullElements
		@ReadOnlyElements
		Map<Object, Pair<Type, List<Type>>> methodTypeMap;

		// public @NonNullElements
		// Map<ASTNode, String> resultStringMap;
		ArrayList<String> rootnodelist;

		protected String resultingString = null;

		public String mainstring = null;

		public String ccodestring = null;

		protected int tempcount = 0;

		protected STGroup stg;

		protected Visitor(@NonNull final SymbolTable st,
				@NonNull final TypeTable tt) throws FileNotFoundException {
			assert (st != null) && (tt != null);
			this.symbolMap = st.symbolMap;
			this.typeMap = tt.typeMap;
			this.methodTypeMap = tt.methodTypeMap;
			this.rootnodelist = new ArrayList<String>();
			stg = new STGroupFile("ccode.stg", "UTF-8", '$', '$');
		}

		protected String getResult() {
			final String result = this.resultingString;
			this.resultingString = null;
			return result;
		}

		protected void setResult(final String t) {
			assert t != null;
			this.resultingString = t;
		}

		protected void dispose() {
			this.symbolMap = null;
			this.typeMap = null;
			this.methodTypeMap = null;
			this.resultingString = null;
			this.mainstring = null;
			this.ccodestring = null;
		}

		/**
		 * Determines whether a {@link List} of {@link Modifier}s has a public
		 * modifier (a {@link Modifier}'s whose {@link ModifierKeyword} is
		 * {@link ModifierKeyword.PUBLIC_KEYWORD}).
		 * 
		 * @param modifiers
		 * @return True, if the {@link List} contains a public modifier.
		 */

		@Override
		public boolean visit(final TypeDeclaration node) {
			// struct <name>{
			// <fields;separator="\n">
			// };<\n>
			ST td = stg.getInstanceOf("typedeclaration");
			String name = node.getName().getIdentifier();
			td.add("name", name);
			for (final Object o : node.getFields()) {
				((ASTNode) o).accept(this);
				String fieldstat = getResult();
				td.add("fields", fieldstat);
			}
			String result = td.render();
			if (this.ccodestring != null)
				this.ccodestring = this.ccodestring + "\r\n" + result;
			else
				this.ccodestring = "#include \"StaticJavaLib.h\"" + "\n"
						+ "#include \"gc.h\"" + "\r\n" + result;
			for (final Object o : node.getMethods()) {
				((ASTNode) o).accept(this);
				String methodstat = getResult();
				if (methodstat.startsWith("void main")) {
					this.mainstring = methodstat;// main method
				} else {
					this.ccodestring = this.ccodestring + "\r\n" + methodstat;// normal
																				// method
				}
			}
			return false;
		}

		@Override
		public boolean visit(final FieldDeclaration node) {
			final VariableDeclarationFragment vdf = (VariableDeclarationFragment) node
					.fragments().get(0);
			String name = vdf.getName().toString();
			Type t = this.typeMap.get(node);
			if (t instanceof IntType || t instanceof BooleanType) {
				String type = node.getType().toString();
				ST fd1 = stg.getInstanceOf("fielddeclaration");
				// <type> <name>;
				if (t instanceof BooleanType)
					fd1.add("type", "bool");
				else
					fd1.add("type", type);
				fd1.add("name", name);
				String result = fd1.render();
				setResult(result);
			} else if (t instanceof ArrayType) {
				ArrayType at = (ArrayType) t;
				String bt = at.baseType.toString();
				ST fd2 = stg.getInstanceOf("fielddeclaration");
				// <type> <name>;
				if (at.baseType instanceof BooleanType)
					fd2.add("type", "bool*");
				else if (at.baseType instanceof IntType)
					fd2.add("type", bt + "*");
				else
					fd2.add("type", "struct " + bt + "**");
				fd2.add("name", name);
				String result = fd2.render();
				setResult(result);
			} else { // ClassType
				ST fd2 = stg.getInstanceOf("fielddeclaration");
				// <type> <name>;
				fd2.add("type", "struct " + t.toString() + "*");
				fd2.add("name", name);
				String result = fd2.render();
				setResult(result);
			}
			return false;
		}

		@Override
		public boolean visit(final VariableDeclarationStatement node) { // method
																		// body
																		// variable:
																		// type
																		// ID
			final VariableDeclarationFragment vdf = (VariableDeclarationFragment) node
					.fragments().get(0);
			String name = vdf.getName().toString();
			Type t = this.typeMap.get(node);

			if (t instanceof IntType || t instanceof BooleanType) {
				String type = node.getType().toString();
				ST fd1 = stg.getInstanceOf("fielddeclaration");
				// <type> <name>;
				if (t instanceof BooleanType)
					fd1.add("type", "bool");
				else
					fd1.add("type", type);
				fd1.add("name", name);
				String result = fd1.render();
				setResult(result);
			} else if (t instanceof ArrayType) {
				ArrayType at = (ArrayType) t;
				String bt = at.baseType.toString();
				ST fd2 = stg.getInstanceOf("fielddeclaration");
				// <basetype>* <name>;
				ST arn = stg.getInstanceOf("addrootnode");
				// addRootNode(address);
				if (at.baseType instanceof BooleanType)
					fd2.add("type", "bool*");
				else if (at.baseType instanceof IntType)
					fd2.add("type", bt + "*");
				else
					// Class type
					fd2.add("type", "struct " + bt + "**");
				fd2.add("name", name);
				arn.add("address", "&" + name);
				this.rootnodelist.add(name);
				String result = fd2.render();
				result = result + "\r\n" + arn.render();
				setResult(result);
			} else { // ClassType
				ST fd2 = stg.getInstanceOf("fielddeclaration");
				// <type> <name>;
				ST arn = stg.getInstanceOf("addrootnode");
				// addRootNode(address);
				fd2.add("type", "struct " + t.toString() + "*");
				fd2.add("name", name);
				arn.add("address", "&" + name);
				this.rootnodelist.add(name);
				String result = fd2.render();
				result = result + "\r\n" + arn.render();
				setResult(result);
			}
			return false;
		}
		

		/*
		 * @Override public boolean visit(final ArrayCreation node){ //we only
		 * consider one-dimension array }
		 */

		@Override
		public boolean visit(final MethodDeclaration node) {
			ST md = stg.getInstanceOf("methoddeclaration");
			// <type> <name>(<args;separator=",">){
			// <stats;separator="\n">
			// }<\n>
			final String className = ((TypeDeclaration) node.getParent())
					.getName().getIdentifier();
			final String methodName = node.getName().getIdentifier();
			final Pair<Type, List<Type>> methodType = this.methodTypeMap
					.get(node);

			if (methodName.equals("main")) {// assign method name return
											// type
				md.add("name", "main");
				md.add("type", "void");
				md.add("args", "int argc");
				md.add("args", "char** argv");
			} else {
				md.add("name", className + "_" + methodName); // classname_methodname
				if (methodType.first instanceof IntType) {
					md.add("type", methodType.first.toString());
				} else if (methodType.first instanceof BooleanType) {
					md.add("type", "bool");
				} else if (methodType.first instanceof ArrayType) {
					ArrayType t = (ArrayType) methodType.first;
					if (t.baseType instanceof IntType) {
						md.add("type", t.baseType.toString() + "*");
					} else if (t.baseType instanceof BooleanType) {
						md.add("type", "bool*");
					} else
						md.add("type", "struct " + t.baseType.toString() + "**");
				} else if (methodType.first instanceof VoidType) {
					md.add("type", "void");
				} else { // ClassType
					md.add("type", "struct " + methodType.first.toString()
							+ "*");
				}

				int i = 0;
				for (final Object o : node.parameters()) {
					((ASTNode) o).accept(this);
					String arg = getResult();
					Type t = methodType.second.get(i);
					if (t instanceof IntType || t instanceof BooleanType) {
						if (t instanceof BooleanType)
							arg = "bool" + " " + arg;
						else
							arg = t.toString() + " " + arg;
					} else if (t instanceof ArrayType) {
						ArrayType at = (ArrayType) t;
						String bt = at.baseType.toString();
						if (at.baseType instanceof IntType)
							arg = bt + "* " + arg;
						else if (at.baseType instanceof BooleanType)
							arg = "bool" + "* " + arg;
						else
							arg = "struct " + bt + "** " + arg;
					} else { // ClassType
						String bt = t.toString();
						arg = "struct " + bt + "* " + arg;
					}
					md.add("args", arg);
					i++;
				}
			}

			boolean returnset = false;
			for (final Object o : node.getBody().statements()) {
				((ASTNode) o).accept(this);
				String stat = getResult();
				if (stat.startsWith("return")) {
					while (!this.rootnodelist.isEmpty()) {
						String name = this.rootnodelist.get(0);
						md.add("stats", "gc_free_root_node(&" + name + ");");
						this.rootnodelist.remove(0);
					}
				}
				if (((ASTNode) o) instanceof ReturnStatement) {
					returnset = true;// record the last return statement
					if (methodName.equals("main"))
						md.add("stats", "gc_recycle();");
				}
				md.add("stats", stat);
			}
			while (!this.rootnodelist.isEmpty()) {
				String name = this.rootnodelist.get(0);
				md.add("stats", "gc_free_root_node(&" + name + ");");
				this.rootnodelist.remove(0);
			}
			if (returnset == false) // {
				md.add("stats", "gc_recycle();");

			String result = md.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final MethodInvocation node) {
			// <name>(<args;separator=",">)
			ST mi = stg.getInstanceOf("methodinvocation");
			final Object o = this.symbolMap.get(node);
			if (o instanceof MethodDeclaration) {
				final MethodDeclaration md = (MethodDeclaration) o;
				final String className = ((TypeDeclaration) md.getParent())
						.getName().getIdentifier();
				// we need the class name, not the class instance name
				final String methodName = node.getName().getIdentifier();
				if (methodName.equals("main")) {
					// void main(int argc, char** argv)
					// public static void main(String[] str)
					mi.add("name", "main");
					int i = 0;
					for (final Object ob : node.arguments()) {
						((ASTNode) ob).accept(this);
						String arg = getResult();
						if (i == 0) {
							String insertedarg = "sizeof(" + arg
									+ ")/sizeof(char*)";
							mi.add("args", insertedarg);
						}
						mi.add("args", arg);
						i++;
					}

				} else {
					mi.add("name", className + "_" + methodName);
				}

			} else if (o instanceof Method) {// static_lib
				final Method m = (Method) o;
				final String className = m.getDeclaringClass().getName()
						.replace('.', '/');
				final String methodName = m.getName();

				if (methodName.equals("println")
						&& node.arguments().size() == 0)
					mi.add("name", className + "_printlnn");
				else if (methodName.equals("println")
						&& node.arguments().size() == 1) {
					for (final Object ob : node.arguments()) {
						Type t = this.typeMap.get((ASTNode) ob);
						if (t instanceof BooleanType)
							mi.add("name", className + "_printlnb");
						if (t instanceof IntType)
							mi.add("name", className + "_printlni");
					}
				} else if (methodName.equals("print")
						&& node.arguments().size() == 1) {
					for (final Object ob : node.arguments()) {
						Type t = this.typeMap.get((ASTNode) ob);
						if (t instanceof BooleanType)
							mi.add("name", className + "_printb");
						if (t instanceof IntType)
							mi.add("name", className + "_printi");
					}
				} else
					mi.add("name", className + "_" + methodName);
			}
			for (final Object ob : node.arguments()) {
				((ASTNode) ob).accept(this);
				String arg = getResult();
				mi.add("args", arg);
			}
			String result = mi.render();
			setResult(result);
			return false;
		}

		
		@Override
		public boolean visit(final ArrayAccess node) {
			ST aa = stg.getInstanceOf("arrayaccess");
			// "<array>[<index>]"
			if (node.getArray() instanceof MethodInvocation) {
				node.getArray().accept(this);
				String arrayExp = getResult();
				aa.add("array", arrayExp);
			} else {
				String arrayName = node.getArray().toString();
				aa.add("array", arrayName);
			}
			node.getIndex().accept(this);
			String index = getResult();
			aa.add("index", index);
			String result = aa.render();
			setResult(result);
			return false;
		}
		
		@Override
		public boolean visit(final Assignment node) {
			final ASTNode rhs = node.getRightHandSide();
			if (rhs instanceof ArrayCreation) {// array type
				node.getLeftHandSide().accept(this);
				String lhs = getResult();
				ArrayCreation rhsexp = (ArrayCreation) rhs;
				ArrayType at = (ArrayType) this.typeMap.get(rhsexp);
				String bt = at.baseType.toString();
				final int numOfdis = rhsexp.dimensions().size();
				if (at.baseType instanceof IntType
						|| at.baseType instanceof BooleanType) {
					if (rhsexp.getInitializer() == null) { // no initializers
						ST ac = stg.getInstanceOf("arraycreation2");
						// <name> = (<type>*)malloc(sizeof(<type>)*(<exp>))
						ac.add("name", lhs);
						if (at.baseType instanceof IntType)
							ac.add("type", bt);
						else
							ac.add("type", "bool");
						for (int i = 0; i < numOfdis; i++) {
							((ASTNode) rhsexp.dimensions().get(i)).accept(this);
							String exp = getResult();
							ac.add("exp", "(" + exp + ")");
						}
						String result = ac.render();
						setResult(result);
					} else {// with initializer
						final ArrayInitializer ai = rhsexp.getInitializer();
						final int numOfinits = ai.expressions().size();
						ST ac = stg.getInstanceOf("arraycreation2");
						// <name> = (<type>*)malloc(sizeof(<type>)*(<exp>))
						// <inits;separator=";\n">
						ac.add("name", lhs);
						if (at.baseType instanceof IntType)
							ac.add("type", bt);
						else
							ac.add("type", "bool");
						ac.add("exp", numOfinits);
						// *(<name>) = <exp>
						for (int i = 0; i < numOfinits; i++) {
							ST ainit = stg.getInstanceOf("arrayinitializer");
							((ASTNode) ai.expressions().get(i)).accept(this);
							String exp = getResult();
							ainit.add("name", lhs + "+" + i);
							ainit.add("exp", exp);
							String init = ainit.render();
							ac.add("inits", init);
						}
						String result = ac.render();
						setResult(result);
					}
				} else { // ClassType
					if (rhsexp.getInitializer() == null) { // no initializers
						ST ac = stg.getInstanceOf("arraycreation1");
						// <name> = (<type>*)malloc(sizeof(<type>)*(<exp>))
						ac.add("name", lhs);
						ac.add("type", "struct " + bt + "*");
						for (int i = 0; i < numOfdis; i++) {
							((ASTNode) rhsexp.dimensions().get(i)).accept(this);
							String exp = getResult();
							ac.add("exp", exp);
						}
						String result = ac.render();
						setResult(result);
					} else {
						final ArrayInitializer ai = rhsexp.getInitializer();
						final int numOfinits = ai.expressions().size();
						ST ac = stg.getInstanceOf("arraycreation2");
						// <name> = (<type>*)malloc(sizeof(<type>)*(<exp>))
						// <inits;separator=";\n">
						ac.add("name", lhs);
						ac.add("type", "struct " + bt + "*");
						ac.add("exp", numOfinits);
						// *(<name>) = <exp>
						for (int i = 0; i < numOfinits; i++) {
							ST ainit = stg.getInstanceOf("arrayinitializer");
							((ASTNode) ai.expressions().get(i)).accept(this);
							String init = getResult();
							ainit.add("name", lhs + "+" + i);
							ainit.add("exp", init);
							String inits = ainit.render();
							ac.add("inits", inits);
						}
						String result = ac.render();
						setResult(result);
					}
				}
			} else {// primitives
				node.getLeftHandSide().accept(this);
				String lhs = getResult();
				node.getRightHandSide().accept(this);
				String exp = getResult();
				ST as = stg.getInstanceOf("assignment");
				as.add("lhs", lhs);
				as.add("exp", exp);
				String result = as.render();
				setResult(result);
			}
			return false;
		}

		@Override
		public boolean visit(Block node) {
			ST block = stg.getInstanceOf("block");
			// <stats;separator="\n">
			for (Object o : node.statements()) {
				((ASTNode) o).accept(this);
				String temp = getResult();
				block.add("stats", temp);
			}
			String result = block.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final BooleanLiteral node) {
			ST bl = stg.getInstanceOf("booleanliteral");
			String value;
			if (node.booleanValue())
				value = "true";
			else
				value = "false";
			bl.add("value", value);
			String result = bl.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ClassInstanceCreation node) {
			// "(<type>*)malloc(sizeof(<type>))"
			ST cic = stg.getInstanceOf("classinstancecreation");
			String type = node.getType().toString();
			cic.add("type", "struct " + type);
			String result = cic.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ConditionalExpression node) {
			ST ce = stg.getInstanceOf("conditionalexpression");
			// "<exp1>?<exp2>:<exp3>"
			if (node.getExpression() != null)
				node.getExpression().accept(this);
			String exp1 = getResult();
			ce.add("exp1", exp1);
			if (node.getThenExpression() != null)
				node.getThenExpression().accept(this);
			String exp2 = getResult();
			ce.add("exp2", exp2);
			if (node.getElseExpression() != null)
				node.getElseExpression().accept(this);
			String exp3 = getResult();
			ce.add("exp3", exp3);
			String result = ce.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final DoStatement node) {
			ST ds = stg.getInstanceOf("dostatement");
			// do{
			// <stats;separator="\n">
			// }while(<exp>);
			if (node.getBody() != null)
				node.getBody().accept(this);
			String statements = getResult();
			if (node.getExpression() != null)
				node.getExpression().accept(this);
			String exp = getResult();
			ds.add("stats", statements);
			ds.add("exp", exp);
			String result = ds.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ExpressionStatement node) {
			node.getExpression().accept(this);
			String exp = getResult();
			ST es = stg.getInstanceOf("expressionstatement");
			// "<if(exp)><exp>;<endif>"
			es.add("exp", exp);
			String result = es.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final FieldAccess node) {
			ST fa = stg.getInstanceOf("fieldaccess");
			// "<class>-><field>"
			node.getExpression().accept(this);
			String classname = getResult();
			fa.add("class", classname);
			String fieldname = node.getName().toString();
			fa.add("field", fieldname);
			String result = fa.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ForStatement node) {
			ST fs = stg.getInstanceOf("forstatement");
			// for(<inits;separator=",">;<exp>;<updaters;separator=",">){
			// <stats;separator="\n">
			// }
			if (node.initializers() != null) {
				for (final Object i : node.initializers()) {
					((ASTNode) i).accept(this);
					String init = getResult();
					fs.add("inits", init);
				}
			}
			if (node.getExpression() != null) {
				node.getExpression().accept(this);
				String exp = getResult();
				fs.add("exp", exp);
			}
			if (node.updaters() != null) {
				for (final Object u : node.updaters()) {
					((ASTNode) u).accept(this);
					String updater = getResult();
					fs.add("updaters", updater);
				}
			}
			if (node.getBody() != null)
				node.getBody().accept(this);// visit block
			String stat = getResult();
			fs.add("stats", stat);

			String result = fs.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final IfStatement node) {
			ST is = stg.getInstanceOf("ifstatement");
			// if(<exp>){
			// <thenstats;separator="\n">
			// }<if(elsestats)>else{
			// <elsestats;separator="\n">
			// }<endif>
			node.getExpression().accept(this);
			String exp = getResult();
			is.add("exp", exp);
			node.getThenStatement().accept(this);
			String thenstat = getResult();
			is.add("thenstats", thenstat);
			final Block elseBlock = (Block) node.getElseStatement();
			if (elseBlock != null) {
				elseBlock.accept(this);
				String elsestat = getResult();
				if (!elsestat.equals(""))
					is.add("elsestats", elsestat);
			}
			String result = is.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final InfixExpression node) {
			ST ife = stg.getInstanceOf("infixexpression");
			// "<lhsexp> <op> <rhsexp>"
			final InfixExpression.Operator op = node.getOperator();
			node.getLeftOperand().accept(this);
			String lhs = getResult();
			node.getRightOperand().accept(this);
			String rhs = getResult();
			if (op == InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED) {
				ife.add("op", ">>");
				ife.add("lhsexp", "((unsigned)(" + lhs + "))");
				ife.add("rhsexp", rhs);
			} else {
				ife.add("lhsexp", lhs);
				ife.add("rhsexp", rhs);
			}
			if (op == InfixExpression.Operator.CONDITIONAL_AND) {
				ife.add("op", "&&");
			} else if (op == InfixExpression.Operator.CONDITIONAL_OR) {
				ife.add("op", "||");
			} else if (op == InfixExpression.Operator.PLUS) {
				ife.add("op", "+");
			} else if (op == InfixExpression.Operator.MINUS) {
				ife.add("op", "-");
			} else if (op == InfixExpression.Operator.TIMES) {
				ife.add("op", "*");
			} else if (op == InfixExpression.Operator.DIVIDE) {
				ife.add("op", "/");
			} else if (op == InfixExpression.Operator.REMAINDER) {
				ife.add("op", "%");
			} else if (op == InfixExpression.Operator.GREATER) {
				ife.add("op", ">");
			} else if (op == InfixExpression.Operator.GREATER_EQUALS) {
				ife.add("op", ">=");
			} else if (op == InfixExpression.Operator.EQUALS) {
				ife.add("op", "==");
			} else if (op == InfixExpression.Operator.NOT_EQUALS) {
				ife.add("op", "!=");
			} else if (op == InfixExpression.Operator.LESS) {
				ife.add("op", "<");
			} else if (op == InfixExpression.Operator.LESS_EQUALS) {
				ife.add("op", "<=");
			} else if (op == InfixExpression.Operator.LEFT_SHIFT) {
				ife.add("op", "<<");
			} else if (op == InfixExpression.Operator.RIGHT_SHIFT_SIGNED) {
				ife.add("op", ">>");
			}
			String result = ife.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final NullLiteral node) {
			ST nl = stg.getInstanceOf("nullliteral");
			nl.add("value", "NULL");
			String result = nl.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final NumberLiteral node) {
			ST nl = stg.getInstanceOf("numberliteral");
			nl.add("value", node.getToken());
			String result = nl.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ParenthesizedExpression node) {
			ST pe = stg.getInstanceOf("parenexpression");
			node.getExpression().accept(this);
			String exp = getResult();
			pe.add("exp", exp);
			String result = pe.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final PostfixExpression node) {
			ST pe = stg.getInstanceOf("postfixexpression");
			// "<operand><operator>"
			node.getOperand().accept(this);
			String operand = getResult();
			pe.add("operand", operand);

			final PostfixExpression.Operator op = node.getOperator();
			if (op == PostfixExpression.Operator.INCREMENT)
				pe.add("operator", "++");
			if (op == PostfixExpression.Operator.DECREMENT)
				pe.add("operator", "--");

			String result = pe.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final PrefixExpression node) {
			ST pe = stg.getInstanceOf("prefixexpression");
			// "<operator><operand>"
			node.getOperand().accept(this);
			String operand = getResult();
			pe.add("operand", operand);
			final PrefixExpression.Operator op = node.getOperator();
			if (op == PrefixExpression.Operator.PLUS)
				pe.add("operator", "+");
			if (op == PrefixExpression.Operator.MINUS)
				pe.add("operator", "-");
			if (op == PrefixExpression.Operator.NOT)
				pe.add("operator", "!");
			if (op == PrefixExpression.Operator.COMPLEMENT)
				pe.add("operator", "~");
			String result = pe.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final ReturnStatement node) {
			ST rs = stg.getInstanceOf("returnstatement");
			// "return<if(exp)> <exp><endif>;"
			final Expression ex = node.getExpression();
			// final Type t = this.typeMap.get(ex);
			if (ex != null) {
				ex.accept(this);
				String exp = getResult();
				rs.add("exp", exp);
			}
			String result = rs.render();
			setResult(result);
			return false;
		}

		@Override
		public boolean visit(final SimpleName node) { // ID
			ST sn = stg.getInstanceOf("simplename");
			// Type t = this.typeMap.get(node);
			sn.add("name", node.getIdentifier());
			String result = sn.render();
			setResult(result);
			return false;
		}


		@Override
		public boolean visit(final WhileStatement node) {
			ST ws = stg.getInstanceOf("whilestatement");
			// while(<exp>){
			// <stats;separator="\n">
			// }
			node.getExpression().accept(this);
			String exp = getResult();
			ws.add("exp", exp);
			node.getBody().accept(this);
			String stat = getResult();
			ws.add("stats", stat);
			String result = ws.render();
			setResult(result);
			return false;
		}
	}

}
